
#pragma once

#include <vector>
#include <iostream>

#include <GL/glew.h>

#include "DCoordinates.h"
#include "HCoordinates.h"

namespace cagd
{
	class CTransformation
	{
	public:

		//
		// T = (matrix[0] matrix[4] matrix[ 8] matrix[12]
		//      matrix[1] matrix[5] matrix[ 9] matrix[13]
		//		matrix[2] matrix[6] matrix[10] matrix[14]
		//		matrix[3] matrix[7] matrix[11] matrix[15])
		//
		std::vector<float> matrix;
		
		// default constructor
		CTransformation()
		{
			matrix.resize(16, 0.0);
			matrix[0] = matrix[5] = matrix[10] = matrix[15] = 1.0;
		}

		// copy constructor
		CTransformation(const CTransformation &T) : matrix(T.matrix)
		{
		}

		// assingment operator
		CTransformation& operator = (const CTransformation &T)
		{
			if (this != &T)
				matrix = T.matrix;
			return *this;
		}

		// add
		CTransformation operator + (const CTransformation &T) const
		{
			CTransformation result(*this);
			for(size_t i = 0; i < 16; i++)
				result.matrix[i] += T.matrix[i];
			return result;
		}

		// sub
		CTransformation operator - (const CTransformation &T) const
		{
			CTransformation result(*this);
			for(size_t i = 0; i < 16; i++)
				result.matrix[i] -= T.matrix[i];
			return result;
		}

		// multiplicate
		CTransformation operator * (const CTransformation &T) const
		{
			CTransformation result;
			for(size_t i = 0; i < 4; i++)
			{
				for (size_t j = 0; j < 4; j++)
				{
					result.matrix[i * 4 + j] = matrix[i * 4 + 0] * T.matrix[i + 0] + matrix[i * 4 + 1] * T.matrix[i + 4] + matrix[i * 4 + 2] * T.matrix[i + 8] + matrix[i * 4 + 3] * T.matrix[i + 12];
				}
			}
			return result;
		}

		// add to this
		CTransformation& operator += (const CTransformation &T)
		{
			for(size_t i = 0; i < 16; i++)
				matrix[i] += T.matrix[i];
			return *this;
		}

		// sub from this
		CTransformation& operator -= (const CTransformation &T)
		{
			for(size_t i = 0; i < 16; i++)
				matrix[i] -= T.matrix[i];
			return *this;
		}

		// multiplicate this by a constant
		CTransformation& operator *= (float constant)
		{
			for(size_t i = 0; i < 16; i++)
				matrix[i] *= constant;
			return *this;
		}

		// divide this by a constant
		CTransformation& operator /= (float constant)
		{
			for(size_t i = 0; i < 16; i++)
				matrix[i] /= constant;
			return *this;
		}

                // multiplicate by a Descartes coordinate, i.e., transform a Descartes coordinate into another one
                DCoordinate operator * (const DCoordinate &dc) const
		{
			float w = matrix[3] * dc.x + matrix[7] * dc.y + matrix[11] * dc.z + matrix[15];
			// note, if w = 0, then transformation matrix is singular
			// so, be careful :)
                        return DCoordinate(
				(matrix[0] * dc.x + matrix[4] * dc.y + matrix[ 8] * dc.z + matrix[12]) / w,
				(matrix[1] * dc.x + matrix[5] * dc.y + matrix[ 9] * dc.z + matrix[13]) / w,
				(matrix[2] * dc.x + matrix[6] * dc.y + matrix[10] * dc.z + matrix[14]) / w);
		}

		// multiplicate by a homogeneous coordinate, i.e., transform a homogeneous coordinate into another one
                HCoordinate operator * (const HCoordinate &hc) const;

		void LoadIdentity()
		{
			for (size_t i = 0; i < 16; i++)
				matrix[i] = (i % 5) ? 0.0f : 1.0f;
		}

		void LoadNullMatrix()
		{
			for (size_t i = 0; i < 16; i++)
				matrix[i] = 0.0f;
		}

		// Is euqal to 0 the determinant of the transformation matrix?
		bool IsSingular() const;

		// perform the transformation
		void Apply() const
		{
			glMultMatrixf(&matrix[0]);
		}

		// virtual destructor
		virtual ~CTransformation()
		{
			matrix.clear();
		}
	};

	// multiplicate the transformation matrix by a constant from left
	inline CTransformation operator * (float lhs, const CTransformation &rhs)
	{
		CTransformation result(rhs);
		for(std::vector<float>::iterator i = result.matrix.begin(); i < result.matrix.end(); i++)
			*i *= lhs;
		return result;
	}

	// multiplicate the transformation matrix by a constant from right
	inline CTransformation operator * (const CTransformation &lhs, float rhs);

	// output to stream
	inline std::ostream& operator << (std::ostream& lhs, const CTransformation &transformation)
	{
		lhs << transformation.matrix[0];
		for(size_t i = 1; i < 16; i++)
			lhs << " " << transformation.matrix[i];
		return lhs;
	}

	// input from stream
	inline std::istream& operator >> (std::istream& lhs, CTransformation &transformation);
}
